3. The CollectionDataContract Attribute
The mechanism shown so far for marshaling a concrete
collection is suboptimal. For one thing, it requires the collection to
be serializable and does not work with the service-oriented DataContract attribute. Also, while one
party is dealing with a collection, the other is dealing with an
array, and the two are not semantically equivalent: the collection is
likely to offer some advantages, or it would not have been chosen in
the first place. Furthermore, there is no compile-time or runtime
verification of the presence of the Add() method or the
IEnumerable and IEnumerable<T> interfaces, resulting
in an unworkable data contract if they are missing. WCF’s solution is
yet another dedicated attribute called CollectionDataContractAttribute, defined
as:
[AttributeUsage(AttributeTargets.Struct|AttributeTargets.Class,Inherited = false)]
public sealed class CollectionDataContractAttribute : Attribute
{
public string Name
{get;set;}
public string Namespace
{get;set;}
//More members
}
The CollectionDataContract
attribute is analogous to the DataContract attribute, and similarly, it
does not make the collection serializable. When applied on a
collection, the CollectionDataContract attribute exposes the
collection to the client as a generic linked list. While the linked
list may have nothing to do with the original collection, it does
offer a more collection-like interface than an array.
For example, given this collection definition:
[CollectionDataContract(Name = "MyCollectionOf{0}")]
public class MyCollection<T> : IEnumerable<T>
{
public void Add(T item)
{}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{...}
//Rest of the implementation
}
and this service-side contract definition:
[ServiceContract]
interface IContactManager
{
[OperationContract]
void AddContact(Contact contact);
[OperationContract]
MyCollection<Contact> GetContacts();
}
the definitions the client ends up with after importing the
metadata will be:
[CollectionDataContract]
public class MyCollectionOfContact : List<Contact>
{}
[ServiceContract]
interface IContactManager
{
[OperationContract]
void AddContact(Contact contact);
[OperationContract]
MyCollectionOfContact GetContacts();
}
In addition, at service load time the CollectionDataContract attribute verifies
the presence of the Add() method as
well as either IEnumerable or
IEnumerable<T>. Failing to
have these on the collection will result in an InvalidDataContractException.
Note that you cannot apply both the DataContract attribute and the CollectionDataContract attribute on a
collection. Again, this is verified at service load time.
4. Referencing a Collection
WCF even lets you preserve the same collection on the
client side as on the service side. The advanced settings dialog box
for the service reference contains a Collection type
combo box that lets you specify how to represent to the client certain
kinds of collections and arrays found in the service metadata. For
example, if the service operation returns one of the collections
IEnumerable<T>,
IList<T>, or
ICollection<T>,
by default the proxy will present it as an array (the default item in
the combo box). However, you can request Visual Studio 2010 to use
another collection, such as BindingList for data binding, a List<T>, Collection, or LinkedList<T>, and so on. If a
conversion is possible, the proxy will use the requested collection
type instead of an array, for example:
[OperationContract]
List<int> GetNumbers();
When you then define the collection in another assembly
referenced by the client’s project, as I’ve discussed, that collection
will be imported as-is. This feature is very useful when interacting
with one of the built-in .NET collections, such as the Stack<T> collection defined in the
System.dll, which is referenced
by practically all .NET projects.
5. Dictionaries
A dictionary is a special type of
collection that maps one data instance to another. As such,
dictionaries do not marshal well either as arrays or as lists. Not
surprisingly, dictionaries get their own representation in WCF.
If the dictionary is a serializable collection that supports the
IDictionary interface, it will be
exposed as a Dictionary<object,object>. For
example, this service contract definition:
[Serializable]
public class MyDictionary : IDictionary
{...}
[ServiceContract]
interface IContactManager
{
...
[OperationContract]
MyDictionary GetContacts();
}
will be exposed as this definition:
[ServiceContract]
interface IContactManager
{
...
[OperationContract]
Dictionary<object,object> GetContacts();
}
This, by the way, includes using the HashTable collection.
If the serializable collection
supports the IDictionary<K,T>
interface, as in:
[Serializable]
public class MyDictionary<K,T> : IDictionary<K,T>
{...}
[ServiceContract]
interface IContactManager
{
...
[OperationContract]
MyDictionary<int,Contact> GetContacts();
}
the exported representation will be as a Dictionary<K,T>:
[ServiceContract]
interface IContactManager
{
...
[OperationContract]
Dictionary<int,Contact> GetContacts();
}
This includes making direct use of Dictionary<K,T> in the original
definition, instead of MyDictionary<K,T>.
If instead of merely being a serializable collection, the
dictionary is decorated with the CollectionDataContract attribute, it will be
marshaled as a subclass of the respective representation. For example,
this service contract definition:
[CollectionDataContract]
public class MyDictionary : IDictionary
{...}
[ServiceContract]
interface IContactManager
{
...
[OperationContract]
MyDictionary GetContacts();
}
will have this representation:
[CollectionDataContract]
public class MyDictionary : Dictionary<object,object>
{}
[ServiceContract]
interface IContactManager
{
...
[OperationContract]
MyDictionary GetContacts();
}
while this generic collection:
[CollectionDataContract(Name = "MyDictionary")]
public class MyDictionary<K,T> : IDictionary<K,T>
{...}
[ServiceContract]
interface IContactManager
{
...
[OperationContract]
MyDictionary<int,Contact> GetContacts();
}
will be published in the metadata as:
[CollectionDataContract]
public class MyDictionary : Dictionary<int,Contact>
{}
[ServiceContract]
interface IContactManager
{
...
[OperationContract]
MyDictionary GetContacts();
}
As for a collection, in the advanced settings dialog box for a
service reference, you can request other dictionary
types, such as the SortedDictionary<T,K>, HashTable, or ListDictionary type, and the proxy will use
that dictionary instead if possible.